﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Chefbook.UserActions;
using Rhino.Mocks;

namespace UnitTests.UserActionsTests
{
    [TestFixture]
    public class UserActionManagerFixture
    {
        private MockRepository mocks;
        private UserActionManager actionManager;


        [SetUp]
        public void Init()
        {
            actionManager = new UserActionManager();
            mocks = new MockRepository();
        }


        [Test]
        public void CanUndoAndCanRedoReturnProperValues()
        {
            IUserAction action = mocks.DynamicMock<IUserAction>();

            Assert.False(actionManager.CanUndo);
            Assert.False(actionManager.CanRedo);
            actionManager.PerformAction(action);
            Assert.True(actionManager.CanUndo);
            Assert.False(actionManager.CanRedo);
            actionManager.Undo();
            Assert.False(actionManager.CanUndo);
            Assert.True(actionManager.CanRedo);
        }

        [Test]
        public void PerformingActionAddsActionToUndoStack()
        {
            IUserAction action = mocks.DynamicMock<IUserAction>();
            
            Expect.Call(action.DoAction);
            
            mocks.ReplayAll();
            
            actionManager.PerformAction(action);
            Assert.AreEqual(action, actionManager.PreviewNextUndoAction(), "Action wasn't added to undo stack");
            Assert.AreEqual(1, actionManager.UndoableActionsCount, "Performed 1 action, but undo stack size was not 1");
            
            mocks.VerifyAll();
        }


        [Test]
        public void PerformingThreeActionAddsActionsToUndoStack()
        {
            IUserAction action1 = mocks.DynamicMock<IUserAction>();
            IUserAction action2 = mocks.DynamicMock<IUserAction>();
            IUserAction action3 = mocks.DynamicMock<IUserAction>();
            
            Expect.Call(action1.DoAction);
            Expect.Call(action2.DoAction);
            Expect.Call(action3.DoAction);

            mocks.ReplayAll();
            
            actionManager.PerformAction(action1);
            actionManager.PerformAction(action2);
            actionManager.PerformAction(action3);
            Assert.AreEqual(action3, actionManager.PreviewNextUndoAction(), "Latest action wasn't next on undo stack");
            Assert.AreEqual(3, actionManager.UndoableActionsCount, "Performed 3 actions, but undo stack size was not 3");
            
            mocks.VerifyAll();
        }

        [Test]
        public void UndoingActionAddsActionToRedoStack()
        {
            IUserAction action = mocks.DynamicMock<IUserAction>();
            
            
            Expect.Call(action.DoAction);
            Expect.Call(action.UndoAction);

            mocks.ReplayAll();

            actionManager.PerformAction(action);
            Assert.DoesNotThrow(()=>actionManager.Undo(), "Action should have been undone successfully but wasn't");
            Assert.AreEqual(1, actionManager.RedoableActionsCount);
            Assert.AreEqual(action, actionManager.PreviewNextRedoAction(), "Action wasn't added to redo stack");
            
            mocks.VerifyAll();
        }

        [Test]
        public void AnUndoneActionCanBeRedone()
        {
            IUserAction action = mocks.DynamicMock<IUserAction>();

            Expect.Call(action.UndoAction);
            Expect.Call(action.DoAction).Repeat.Twice();

            mocks.ReplayAll();

            actionManager.PerformAction(action);
            Assert.DoesNotThrow(() => actionManager.Undo(), "Action wasn't undone");
            Assert.AreEqual(0, actionManager.UndoableActionsCount, "Undoable actions count after undo");
            Assert.DoesNotThrow(() => actionManager.Redo(), "Action wasn't redone");
            Assert.AreEqual(0, actionManager.RedoableActionsCount, "Redoable actions count after redo");
            Assert.AreEqual(1, actionManager.UndoableActionsCount, "Undoable actions count after redo");

            mocks.VerifyAll();
        }

        [Test]
        public void UndoThrowsInvalidOperationExceptionWhenNoActionPeformed()
        {
            Assert.Null(actionManager.PreviewNextUndoAction(), "Next Undo action was not null when no action to undo");
            Assert.Throws<InvalidOperationException>(() => actionManager.Undo(), "Action was undone successfully when no action was performed");
        }

        [Test]
        public void RedoThrowsInvalidOperationExceptionWhenNoActionPeformed()
        {
            IUserAction action = mocks.DynamicMock<IUserAction>();
            
            Expect.Call(action.DoAction).Repeat.Once();

            mocks.ReplayAll();

            actionManager.PerformAction(action);
            Assert.Null(actionManager.PreviewNextRedoAction(), "Next Redo action was not null when no action was undone");
            Assert.Throws<InvalidOperationException>(() => actionManager.Redo(), "Action was redone successfully when no action was undone");
            
            mocks.VerifyAll();
        }

        [Test]
        public void RedoStackIsResetWhenNewActionPeformed()
        {
            IUserAction action1 = mocks.DynamicMock<IUserAction>();
            IUserAction action2 = mocks.DynamicMock<IUserAction>();
            IUserAction action3 = mocks.DynamicMock<IUserAction>();

            Expect.Call(action1.DoAction).Repeat.Once().Message("Action1 Do");
            Expect.Call(action2.DoAction).Repeat.Once().Message("Action2 Do");
            Expect.Call(action3.DoAction).Repeat.Once().Message("Action3 Do");
            Expect.Call(action1.UndoAction).Repeat.Once().Message("Action1 Undo");
            Expect.Call(action2.UndoAction).Repeat.Once().Message("Action2 Undo");
            Expect.Call(action3.UndoAction).Repeat.Never().Message("Action3 Undo");

            mocks.ReplayAll();

            actionManager.PerformAction(action1);
            actionManager.PerformAction(action2);
            Assert.DoesNotThrow(() => actionManager.Undo());
            Assert.DoesNotThrow(() => actionManager.Undo());
            actionManager.PerformAction(action3);
            Assert.AreEqual(0, actionManager.RedoableActionsCount, "More than 0 redoable actions after performing new action");
            Assert.Null(actionManager.PreviewNextRedoAction(), "Next Undoable action not null");

            mocks.VerifyAll();
        }
    }
}
